#include "newsourcewizard.h"

#include <QScreen>
#include <QGroupBox>

NewSourceWizard::NewSourceWizard(Project *project, QWidget* parent) :
    QWizard(parent)
{
    // 初始化页面
    page1 = new NewSourcePage1(project);
    page2 = new NewSourcePage2;
    addPage(page1);
    addPage(page2);

    // 设置属性
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowTitle(tr("New Source Wizard"));
    setWizardStyle(QWizard::ClassicStyle);

    // 设置窗口位置
    QRect screenRect = screen()->availableGeometry();
    screenRect.moveTo(screenRect.center().x() - screenRect.width() / 8,
                      screenRect.center().y() - screenRect.height() / 4) ;
    screenRect.setWidth(screenRect.width() / 4);
    screenRect.setHeight(screenRect.height() / 2);
    setGeometry(screenRect);
}

void NewSourceWizard::accept()
{
    QDir dir;
    QString srcName = page1->sourceName();
    QString srcPath = page1->sourcePath();

    QFile srcFile(srcPath);
    if (dir.exists(srcPath)) { // 若该源文件已存在，让用户选择是否覆盖
        int ans = QMessageBox::question(this, tr("New Source Wizard"), QString(tr("Source \"%1\" is already existed, replace it?")).arg(srcName),
                                        QMessageBox::Yes|QMessageBox::Cancel,QMessageBox::Cancel);
        if (ans == QMessageBox::Cancel) {
            return;
        }
    } else { // 否则创建该文件并准备写入
        if (!srcFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Cannot create %1").arg(srcPath));
            return;
        }
    }

    // 源文件模板，包括时间刻度、模块名、参数列表、端口列表、端口声明列表
    QString text = QString("`timescale %1/%2\n"
                           "\n"
                           "module %3 %4 %5;\n"
                           "%6\n\n"
                           "endmodule\n").arg(page2->timeunit())
                                       .arg(page2->timeprecision())
                                       .arg(page1->moduleName())
                                       .arg(page2->parameterList())
                                       .arg(page2->portList())
                                       .arg(page2->portDeclarationList());

    // 写入
    QTextStream stream(&srcFile);
    stream.setEncoding(QStringConverter::Utf8);
    stream << text;
    srcFile.flush();
    srcFile.close();

    close();
    emit wizardFinished(srcPath);
}

NewSourcePage1::NewSourcePage1(Project *project, QWidget *parent) :
    QWizardPage(parent),
    m_project(project)
{
    // 初始化各部件
    QLabel *sourceNameLabel = new QLabel(tr("Source Name:"));
    sourceNameEdit = new QLineEdit;
    sourceTypeCombo = new QComboBox;
    sourceTypeCombo->addItem(".v");
    sourceTypeCombo->setMaximumSize(sourceNameEdit->height(), sourceNameEdit->height());
    NoMarginHBoxLayout *nameLayout = new NoMarginHBoxLayout;
    sourceNameEdit->setLayout(nameLayout);
    nameLayout->addStretch();
    nameLayout->addWidget(sourceTypeCombo);
    sourceNameEdit->setTextMargins(sourceNameEdit->textMargins().left(),
                                   sourceNameEdit->textMargins().top(),
                                   30,
                                   sourceNameEdit->textMargins().bottom());
    QLabel *moduleNameLabel = new QLabel(tr("Module Name:"));
    moduleNameEdit = new QLineEdit;
    sourcePathEdit = new ShowDirLineEdit(tr("Source Path:"), tr("Browse"));    

    // 设置布局
    setLayout(new NoMarginVBoxLayout);
    layout()->addWidget(sourceNameLabel);
    layout()->addWidget(sourceNameEdit);
    layout()->addWidget(moduleNameLabel);
    layout()->addWidget(moduleNameEdit);
    layout()->addWidget(sourcePathEdit);
    setTitle(tr("Specify source name and path"));

    // 源文件名改变时，模块名、源文件路径随之改变
    connect(sourceNameEdit, &QLineEdit::textChanged, this, [&](const QString &text) {
        moduleNameEdit->setText(text);
        sourcePathEdit->setText(upPath + "/" + text + sourceTypeCombo->currentText());
        emit completeChanged();
    });
    connect(sourceNameEdit, &QLineEdit::textEdited, this, &NewSourcePage1::completeChanged);
    // 路径改变时，上一级目录路径也改变
    connect(sourcePathEdit, &ShowDirLineEdit::textEdited, this, [&](const QString &text) {
        upPath = text;
        emit completeChanged();
    });
    // 浏览路径按钮按下时，弹出浏览目录窗口
    connect(sourcePathEdit->button(), &QPushButton::clicked, this, [=]() {
        QString sourcePath = QFileDialog::getExistingDirectory(this, "Source Path", m_project->sourcePath());
        if (sourcePath.isEmpty()) {
            return;
        }
        upPath = sourcePath;
        sourcePathEdit->setText(QDir(sourcePath).absoluteFilePath(sourceNameEdit->text()));
    });
}

void NewSourcePage1::initializePage()
{
    sourcePathEdit->setText(m_project->sourcePath());
    upPath = m_project->sourcePath();
}

bool NewSourcePage1::isComplete() const
{
    return !sourceName().isEmpty() && !moduleName().isEmpty() && !sourcePath().isEmpty();
}

QString NewSourcePage1::sourceName() const
{
    return sourceNameEdit->text();
}

QString NewSourcePage1::moduleName() const
{
    return moduleNameEdit->text();
}

QString NewSourcePage1::sourcePath() const
{
    return sourcePathEdit->text();
}

NewSourcePage2::NewSourcePage2(QWidget *parent)
    : QWizardPage(parent)
{
    QGroupBox *timescaleGroup = new QGroupBox(tr("Set Timescale"), this);
    QHBoxLayout *timescaleLayout = new QHBoxLayout;
    timescaleGroup->setLayout(timescaleLayout);
    QStringList timeList{"1fs", "10fs", "100fs",
                        "1ps", "10ps", "100ps",
                        "1ns", "10ns", "100ns",
                        "1us", "10us", "100us",
                        "1ms", "10ms", "100ms",
                        "1s", "10s", "100s"};
    timeUnitCombo = new QComboBox(this);
    timeUnitCombo->setEditable(false);
    timeUnitCombo->addItems(timeList);
    timeUnitCombo->setCurrentText("1ns");
    QLabel *splitLabel = new QLabel("/", this);
    timePrecisionCombo = new QComboBox(this);
    timePrecisionCombo->setEditable(false);
    timePrecisionCombo->addItems(timeList);
    timePrecisionCombo->setCurrentText("1ps");
    timescaleLayout->addSpacerItem(new QSpacerItem(250,2));
    timescaleLayout->addWidget(timeUnitCombo);
    timescaleLayout->addWidget(splitLabel);
    timescaleLayout->addWidget(timePrecisionCombo);
    timescaleLayout->addSpacerItem(new QSpacerItem(250,2));

    QGroupBox *parameterGroup = new QGroupBox(tr("Set Parameters"), this);
    parameterGroup->setLayout(new QHBoxLayout);
    parameterTable = new QTableView(this);
    parameterTable->setEditTriggers(QTableView::AllEditTriggers);
    parameterModel = new QStandardItemModel(this);
    parameterModel->setColumnCount(2);
    parameterModel->setHeaderData(0, Qt::Horizontal, tr("Name"));
    parameterModel->setHeaderData(1, Qt::Horizontal, tr("Default Value"));
    parameterTable->setModel(parameterModel);
    addParameterButton = new QPushButton(tr("Add"), this);
    removeParameterButton = new QPushButton(tr("Remove"), this);
    connect(addParameterButton, &QPushButton::clicked, this, [&] {
        parameterModel->appendRow(QList<QStandardItem *>()
                                  << new QStandardItem
                                  << new QStandardItem);
    });
    connect(removeParameterButton, &QPushButton::clicked, this, [&] {
        if (parameterTable->currentIndex().isValid()) {
            parameterModel->removeRow(parameterTable->currentIndex().row());
        }
    });
    QWidget *parameterButtonWidget = new QWidget(this);
    parameterButtonWidget->setLayout(new QVBoxLayout);
    parameterButtonWidget->layout()->addWidget(addParameterButton);
    parameterButtonWidget->layout()->addWidget(removeParameterButton);
    parameterGroup->layout()->addWidget(parameterTable);
    parameterGroup->layout()->addWidget(parameterButtonWidget);


    QGroupBox *portGroup = new QGroupBox(tr("Set Ports"), this);
    portGroup->setLayout(new QHBoxLayout);
    portTable = new QTableView(this);
    portTable->setEditTriggers(QTableView::AllEditTriggers);
    portModel = new QStandardItemModel(this);
    portModel->setColumnCount(6);
    portModel->setHeaderData(0, Qt::Horizontal, tr("Direction"));
    portModel->setHeaderData(1, Qt::Horizontal, tr("Type"));
    portModel->setHeaderData(2, Qt::Horizontal, tr("Sign"));
    portModel->setHeaderData(3, Qt::Horizontal, tr("Name"));
    portModel->setHeaderData(4, Qt::Horizontal, tr("MSB"));
    portModel->setHeaderData(5, Qt::Horizontal, tr("LSB"));
    portTable->setModel(portModel);
    portTable->setItemDelegateForColumn(0, new ComboBoxItemDelegate(QStringList() << "input" << "output" << "inout", this));
    portTable->setItemDelegateForColumn(1, new ComboBoxItemDelegate(QStringList() << "wire" << "reg" << "integer" << "supply0" << "supply1" << "tri" << "tri1" << "tri0" << "triand" << "trior" << "trireg" << "wand" << "wor", this));
    portTable->setItemDelegateForColumn(2, new ComboBoxItemDelegate(QStringList() << "unsigned" << "signed", this));
    addPortButton = new QPushButton(tr("Add"), this);
    removePortButton = new QPushButton(tr("Remove"), this);
    connect(addPortButton, &QPushButton::clicked, this, [&] {
        QList<QStandardItem *> itemList;
        itemList << new QStandardItem("input")
                 << new QStandardItem("wire")
                 << new QStandardItem("unsigned")
                 << new QStandardItem
                 << new QStandardItem
                 << new QStandardItem;
        portModel->appendRow(itemList);
    });
    connect(removePortButton, &QPushButton::clicked, this, [&] {
        if (portTable->currentIndex().isValid()) {
            portModel->removeRow(portTable->currentIndex().row());
        }
    });
    QWidget *portButtonWidget = new QWidget(this);
    portButtonWidget->setLayout(new QVBoxLayout);
    portButtonWidget->layout()->addWidget(addPortButton);
    portButtonWidget->layout()->addWidget(removePortButton);
    portGroup->layout()->addWidget(portTable);
    portGroup->layout()->addWidget(portButtonWidget);

    setLayout(new NoMarginVBoxLayout);
    layout()->addWidget(timescaleGroup);
    layout()->addWidget(parameterGroup);
    layout()->addWidget(portGroup);
}

bool NewSourcePage2::validatePage()
{
    if (timeUnitCombo->currentIndex() < timePrecisionCombo->currentIndex()) {
        QMessageBox::critical(this, tr("New Source Wizard"), tr("Time unit should not be less than time precision!"));
        return false;
    }
    QStringList parameterNames;
    int paramCount = parameterModel->rowCount();
    for (int i = 0; i < paramCount; ++i) {
        QString name = parameterModel->item(i, 0)->data(Qt::DisplayRole).toString();
        if (name.isEmpty()) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Parameter%1's name must not be empty!").arg(i + 1));
            return false;
        }
        static QRegularExpressionValidator re_name(QRegularExpression(R"([_a-zA-Z][_$0-9a-zA-Z]*)"));
        int pos = 0;
        if (re_name.validate(name, pos) != QValidator::Acceptable) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Invalid parameter name: %1.").arg(name));
            return false;
        }
        if (parameterNames.contains(name)) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Dumplicated parameter name: %1.").arg(name));
            return false;
        }
        parameterNames << name;
    }

    QStringList portNames;
    int portCount = portModel->rowCount();
    for (int i = 0; i < portCount; ++i) {
        QString name = portModel->item(i, 3)->data(Qt::DisplayRole).toString();
        if (name.isEmpty()) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Port%1's name must not be empty!").arg(i + 1));
            return false;
        }
        static QRegularExpressionValidator re_name(QRegularExpression(R"([_a-zA-Z][_$0-9a-zA-Z]*)"));
        int pos = 0;
        if (re_name.validate(name, pos) != QValidator::Acceptable) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Invalid port name: \"%1\".").arg(name));
            return false;
        }
        if (portNames.contains(name)) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("Dumplicated port name: \"%1\".").arg(name));
            return false;
        }
        QString direction = portModel->item(i, 0)->data(Qt::DisplayRole).toString();
        QString type = portModel->item(i, 1)->data(Qt::DisplayRole).toString();
        if ((direction == "input" || direction == "inout") && (type == "reg" || type == "integer")) {
            QMessageBox::critical(this, tr("New Source Wizard"), tr("%1 port \"%2\" cannot be %3.").arg(direction).arg(name).arg(type));
            return false;
        }
        portNames << name;
    }
    return true;
}

QString NewSourcePage2::timeunit() const
{
    return timeUnitCombo->currentText();
}

QString NewSourcePage2::timeprecision() const
{
    return timePrecisionCombo->currentText();
}

QString NewSourcePage2::parameterList() const
{
    QStringList lst;
    int paramCount = parameterModel->rowCount();
    if (paramCount == 0) {
        return QString();
    }
    for (int i = 0; i < paramCount; ++i) {
        QString name = parameterModel->item(i, 0)->data(Qt::DisplayRole).toString();
        QString value = parameterModel->item(i, 1)->data(Qt::DisplayRole).toString();
        if (value.isEmpty()) {
            lst << QString("\tparameter %1").arg(name);
        } else {
            lst << QString("\tparameter %1 = %2").arg(name).arg(value);
        }
    }

    return QString(" #(\n"
           "%1\n"
           ")").arg(lst.join(",\n"));
}

QString NewSourcePage2::portList() const
{
    QStringList lst;
    int portCount = portModel->rowCount();
    if (portCount == 0) {
        return QString();
    }
    for (int i = 0; i < portCount; ++i) {
        QString name = portModel->item(i, 3)->data(Qt::DisplayRole).toString();
        lst << QString("\t%1").arg(name);
    }
    return QString("(\n"
                   "%1\n"
                   ")").arg(lst.join(",\n"));
}

QString NewSourcePage2::portDeclarationList() const
{
    QStringList lst;
    int portCount = portModel->rowCount();
    for (int i = 0; i < portCount; ++i) {
        QString direction = portModel->item(i, 0)->data(Qt::DisplayRole).toString();
        QString type = portModel->item(i, 1)->data(Qt::DisplayRole).toString();
        QString sign = portModel->item(i, 2)->data(Qt::DisplayRole).toString();
        QString name = portModel->item(i, 3)->data(Qt::DisplayRole).toString();
        QString msb = portModel->item(i, 4)->data(Qt::DisplayRole).toString();
        QString lsb = portModel->item(i, 5)->data(Qt::DisplayRole).toString();
        if (msb.isEmpty()) {
            msb = "0";
        }
        if (lsb.isEmpty()) {
            lsb = "0";
        }
        if (sign == "unsigned") {
            if (msb == "0" && lsb == "0") {
                lst << QString("\t%1 %2;\n"
                               "\t%3 %2;\n").arg(direction).arg(name).arg(type);
            } else {
                lst << QString("\t%1[%4:%5] %2;\n"
                               "\t%3[%4:%5] %2;\n").arg(direction).arg(name).arg(type).arg(msb).arg(lsb);
            }
        } else {
            if (msb == "0" && lsb == "0") {
                lst << QString("\t%1 signed %2;\n"
                               "\t%3 signed %2;\n").arg(direction).arg(name).arg(type);
            } else {
                lst << QString("\t%1 signed[%4:%5] %2;\n"
                               "\t%3 signed[%4:%5] %2;\n").arg(direction).arg(name).arg(type).arg(msb).arg(lsb);
            }
        }
    }
    return lst.join("\n");
}
